package com.iaroc.irobot.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Hashtable;

import javax.microedition.rms.RecordEnumeration;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;

import com.sun.squawk.util.UnexpectedException;

public class KeyedStorage {
    protected static final int TYPE_STRING = 1;
    protected static final int TYPE_BYTES = 2;
    
    RecordStore recordStore;
    Hashtable keys;
    
    public KeyedStorage(String name) {
        try {
            recordStore = RecordStore.openRecordStore(name, true);
        } catch (RecordStoreException e) {
            throw new UnexpectedException(e);
        }
        readKeys();
    }

     /**
     * Returns the value to which the specified key is mapped in this hashtable.
     * 
     * @param key
     *            a key in the hashtable.
     * @return the value to which the key is mapped in this hashtable;
     *         <code>null</code> if the key is not mapped to any value in this
     *         hashtable.
     * @see KeyedStorage#put(java.lang.Object, java.lang.Object)
     */
    public synchronized byte[] getBytes(String key) {
        Integer recordId = (Integer) keys.get(key);
        if (recordId == null) {
            return null;
        }
        try {
            byte[] record = recordStore.getRecord(recordId.intValue());
            DataInputStream input = new DataInputStream(new ByteArrayInputStream(record));
            input.readUTF();
            int type = input.readUnsignedByte();
            if (type != TYPE_BYTES) {
                throw new IllegalStateException("Requested bytes, but stored content was something else");
            }
            byte[] value = new byte[input.available()];
            input.readFully(value);
            return value;
        } catch (RecordStoreException e) {
            throw new UnexpectedException(e);
        } catch (IOException e) {
            throw new UnexpectedException(e);
        }
    }
     
     /**
     * Returns the value to which the specified key is mapped in this hashtable.
     * 
     * @param key
     *            a key in the hashtable.
     * @return the value to which the key is mapped in this hashtable;
     *         <code>null</code> if the key is not mapped to any value in this
     *         hashtable.
     * @see KeyedStorage#put(java.lang.Object, java.lang.Object)
     */
    public synchronized String getString(String key) {
        Integer recordId = (Integer) keys.get(key);
        if (recordId == null) {
            return null;
        }
        try {
            byte[] record = recordStore.getRecord(recordId.intValue());
            DataInputStream input = new DataInputStream(new ByteArrayInputStream(record));
            input.readUTF();
            int type = input.readUnsignedByte();
            if (type != TYPE_STRING) {
                throw new IllegalStateException("Requested String, but stored content was something else");
            }
            String value = input.readUTF();
            return value;
        } catch (RecordStoreException e) {
            throw new UnexpectedException(e);
        } catch (IOException e) {
            throw new UnexpectedException(e);
        }
    }
     
     /**
      * Maps the specified <code>key</code> to the specified
      * <code>value</code> in this hashtable. Neither the key nor the
      * value can be <code>null</code>.
      * <p>
      * The value can be retrieved by calling the <code>get</code> method
      * with a key that is equal to the original key.
      *
      * @param      key     the hashtable key.
      * @param      value   the value.
      * @return     the previous value of the specified key in this hashtable,
      *             or <code>null</code> if it did not have one.
      * @exception  NullPointerException  if the key or value is
      *               <code>null</code>.
      */
     public synchronized byte[] putBytes(String key, byte[] value) {
         Integer recordId = (Integer) keys.get(key);
         ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(value.length + key.length() + 10);
         DataOutputStream dataOut = new DataOutputStream(bytesOut);
         try {
            dataOut.writeUTF(key);
            dataOut.writeByte(TYPE_BYTES);
            dataOut.write(value);
            byte[] record = bytesOut.toByteArray();
            if (recordId == null) {
                int id = recordStore.addRecord(record, 0, record.length);
                recordId = new Integer(id);
                keys.put(key, recordId);
            } else {
                recordStore.setRecord(recordId.intValue(), record, 0, record.length);
            }
            return value;
        } catch (IOException e) {
            throw new UnexpectedException(e);
        } catch (RecordStoreException e) {
            throw new UnexpectedException(e);
        }
     }

     /**
      * Maps the specified <code>key</code> to the specified
      * <code>value</code> in this hashtable. Neither the key nor the
      * value can be <code>null</code>.
      * <p>
      * The value can be retrieved by calling the <code>get</code> method
      * with a key that is equal to the original key.
      *
      * @param      key     the hashtable key.
      * @param      value   the value.
      * @return     the previous value of the specified key in this hashtable,
      *             or <code>null</code> if it did not have one.
      * @exception  NullPointerException  if the key or value is
      *               <code>null</code>.
      */
     public synchronized String putString(String key, String value) {
         Integer recordId = (Integer) keys.get(key);
         ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(value.length() + key.length() + 10);
         DataOutputStream dataOut = new DataOutputStream(bytesOut);
         try {
            dataOut.writeUTF(key);
            dataOut.writeByte(TYPE_STRING);
            dataOut.writeUTF(value);
            byte[] record = bytesOut.toByteArray();
            if (recordId == null) {
                int id = recordStore.addRecord(record, 0, record.length);
                recordId = new Integer(id);
                keys.put(key, recordId);
            } else {
                recordStore.setRecord(recordId.intValue(), record, 0, record.length);
            }
            return value;
        } catch (IOException e) {
            throw new UnexpectedException(e);
        } catch (RecordStoreException e) {
            throw new UnexpectedException(e);
        }
     }

     protected void readKeys() {
        keys = new Hashtable();
        try {
            RecordEnumeration recordEnumeration = recordStore.enumerateRecords(null, null, false);
            while (recordEnumeration.hasNextElement()) {
                int recordId = recordEnumeration.nextRecordId();
                byte[] bytes = recordStore.getRecord(recordId);
                String key = new DataInputStream(new ByteArrayInputStream(bytes)).readUTF();
                keys.put(key, new Integer(recordId));
            }
        } catch (RecordStoreException e) {
            throw new UnexpectedException(e);
        } catch (IOException e) {
            throw new UnexpectedException(e);
        }
    }
    
     /**
      * Removes the key (and its corresponding value) from this
      * hashtable. This method does nothing if the key is not in the hashtable.
      *
      * @param   key   the key that needs to be removed.
      */
     public synchronized void remove(String key) {
         Integer recordId = (Integer) keys.remove(key);
         if (recordId == null) {
             return;
         }
         try {
            recordStore.deleteRecord(recordId.intValue());
        } catch (RecordStoreException e) {
            throw new UnexpectedException(e);
        }
     }

}
